﻿using System;
using System.ComponentModel;
using System.IO;
using System.Reflection;
using System.Windows.Forms;

namespace Moonlite.Data.Configuration
{
    /// <summary>
    /// Represents an <see cref="System.Reflection.Assembly"/>/<see cref="ConfigurationInstance"/> pair; that is, a configuration instance that has been attached to a <see cref="System.Reflection.Assembly"/>.
    /// </summary>
    public sealed class AssemblyConfigurationAttachment
    {
        Assembly assembly;
        string configurationStoragePath;
        Type configurationInstanceType;
        ConfigurationInstance configurationInstance;
        bool loaded;
        bool autoSave;

        /// <summary>
        /// Gets the assembly.
        /// </summary>
        /// <value>The assembly.</value>
        public Assembly Assembly
        {
            get { return assembly; }
        }

        /// <summary>
        /// Gets the configuration storage path.
        /// </summary>
        /// <value>The configuration storage path.</value>
        public string ConfigurationStoragePath
        {
            get { return configurationStoragePath; }
        }

        /// <summary>
        /// Gets the type of the configuration instance.
        /// </summary>
        /// <value>The type of the configuration instance.</value>
        public Type ConfigurationInstanceType
        {
            get { return configurationInstanceType; }
        }

        /// <summary>
        /// Gets or sets a value indicating whether the ConfigurationInstance should automatically save itself when a property has been changed.
        /// </summary>
        /// <value><c>true</c> if the ConfigurationInstance should automatically save itself when a property has been changed; otherwise, <c>false</c>.</value>
        public bool AutoSave
        {
            get { return autoSave; }
            set { autoSave = value; }
        }

        /// <summary>
        /// Gets or sets the configuration instance.
        /// </summary>
        /// <value>The configuration instance.</value>
        public ConfigurationInstance ConfigurationInstance
        {
            get { return configurationInstance; }
            internal set
            {
                if (configurationInstance != null)
                {
                    configurationInstance.PropertyChanged -= OnConfigurationInstancePropertyChanged;
                }

                configurationInstance = value;

                if (configurationInstance != null)
                {
                    configurationInstance.PropertyChanged += OnConfigurationInstancePropertyChanged;
                }
            }
        }

        /// <summary>
        /// Gets or sets a value indicating whether this <see cref="AssemblyConfigurationAttachment"/> is loaded.
        /// </summary>
        /// <value><c>true</c> if loaded; otherwise, <c>false</c>.</value>
        internal bool Loaded
        {
            get { return loaded; }
            set { loaded = value; }
        }

        /// <summary>
        /// Loads this instance.
        /// </summary>
        internal void Load()
        {
            if (loaded)
            {
                throw new InvalidOperationException("This AssemblyConfigurationAttachment instance is already loaded.");
            }

            loaded = true;
            if (File.Exists(configurationStoragePath))
            {
                configurationInstance = ConfigurationManager.CreateConfigurationInstanceFromConfigurationDocument(configurationStoragePath);
            }
            else
            {
                configurationInstance = ConfigurationManager.CreateConfigurationInstanceAndFillProperties(configurationInstanceType);
                ConfigurationManager.SaveConfigurationInstanceToConfigurationDocument(configurationInstance, configurationStoragePath, false);
            }
        }

        /// <summary>
        /// Called when a <see cref="ConfigurationInstance"/>'s <see cref="ConfigurationElement"/> property has changed.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="System.ComponentModel.PropertyChangedEventArgs"/> instance containing the event data.</param>
        private void OnConfigurationInstancePropertyChanged(object sender, PropertyChangedEventArgs e)
        {
            if (autoSave)
            {
                ConfigurationManager.SaveConfigurationInstanceToConfigurationDocument(configurationInstance, configurationStoragePath, true);
            }
        }

        /// <summary>
        /// Initialization/constructor core.
        /// </summary>
        /// <param name="assembly">The assembly.</param>
        /// <param name="configurationStoragePath">The configuration storage path.</param>
        /// <param name="configurationInstanceType">The configuration instance type.</param>
        internal void CoreInitialize(Assembly assembly, string configurationStoragePath, Type configurationInstanceType)
        {
            this.assembly = assembly;
            this.configurationStoragePath = configurationStoragePath;
            this.configurationInstanceType = configurationInstanceType;
            AttachmentRegister.RegisterPair(this, false);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyConfigurationAttachment"/> class.
        /// </summary>
        /// <param name="assembly">The assembly to attach.</param>
        /// <param name="configurationInstanceType">The configuration instance type.</param>
        public AssemblyConfigurationAttachment(Assembly assembly, Type configurationInstanceType)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }

            string assemblyPath = AssemblyPathHelpers.GetAssemblyPath(assembly);
            configurationStoragePath = Path.GetDirectoryName(assemblyPath) + "\\" + Path.GetFileNameWithoutExtension(assemblyPath) + ".config";
            CoreInitialize(assembly, configurationStoragePath, configurationInstanceType);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyConfigurationAttachment"/> class.
        /// </summary>
        /// <param name="filename">The path to the assembly to attach.</param>
        /// <param name="configurationStoragePath">The configuration storage path.</param>
        /// <param name="configurationInstanceType">The configuration instance type.</param>
        public AssemblyConfigurationAttachment(string filename, string configurationStoragePath, Type configurationInstanceType)
        {
            if (string.IsNullOrEmpty(filename))
            {
                throw new ArgumentNullException("filename");
            }

            if (!File.Exists(filename))
            {
                throw new FileNotFoundException();
            }

            if (string.IsNullOrEmpty(configurationStoragePath))
            {
                throw new ArgumentNullException("configurationStoragePath");
            }

            Assembly assembly = Assembly.LoadFile(filename);
            CoreInitialize(assembly, configurationStoragePath, configurationInstanceType);
        }

        /// <summary>
        /// Initializes a new instance of the <see cref="AssemblyConfigurationAttachment"/> class.
        /// </summary>
        /// <param name="assembly">The assembly to attach.</param>
        /// <param name="configurationStoragePath">The configuration storage path.</param>
        /// <param name="configurationInstanceType">The configuration instance type.</param>
        public AssemblyConfigurationAttachment(Assembly assembly, string configurationStoragePath, Type configurationInstanceType)
        {
            if (assembly == null)
            {
                throw new ArgumentNullException("assembly");
            }

            if (string.IsNullOrEmpty(configurationStoragePath))
            {
                throw new ArgumentNullException("configurationStoragePath");
            }

            CoreInitialize(assembly, configurationStoragePath, configurationInstanceType);
        }
    }
}
